home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue65 / alfresco / TraverseNFA.dpr < prev    next >
Encoding:
Text File  |  2000-11-28  |  8.3 KB  |  294 lines

  1. {*********************************************************}
  2. {* TraverseNFA                                           *}
  3. {* Copyright (c) Julian M Bucknall 2001                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: NFA traversal routine            *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. program TraverseNFA;
  14.  
  15. {$apptype console}
  16.  
  17. uses
  18.   SysUtils,
  19.   Classes;
  20.  
  21. type
  22.   PaaCharSet = ^TaaCharSet;
  23.   TaaCharSet = set of char;
  24.  
  25.   TaaNFAMatchType = (  {types of matching performed...}
  26.      mtNone,           {..no match (an epsilon no-cost move)}
  27.      mtAnyChar,        {..any character}
  28.      mtChar,           {..a particular character}
  29.      mtClass,          {..a character class}
  30.      mtNegClass);      {..a negated character class}
  31.  
  32.   TaaNFAStateData = record
  33.     sdNextState1: integer;
  34.     sdNextState2: integer;            {-1 means "not used"}
  35.     sdMatchType : TaaNFAMatchType;
  36.     case integer of
  37.       0 : (sdChar  : char);
  38.       1 : (sdClass : PaaCharSet);
  39.   end;
  40.  
  41.   PaaNFAStateTable = ^TaaNFAStateTable;
  42.   TaaNFAStateTable = packed record
  43.     stStartState: integer;
  44.     stFinalState: integer;
  45.     stTable     : array [0..9999] of TaaNFAStateData;
  46.   end;
  47.  
  48. {====================================================================}
  49. type
  50.   TaaIntDeque = class
  51.     private
  52.       FList : TList;
  53.       FHead : integer;
  54.       FTail : integer;
  55.     protected
  56.       procedure idGrow;
  57.     public
  58.       constructor Create(aCapacity : integer);
  59.       destructor Destroy; override;
  60.  
  61.       function IsEmpty : boolean;
  62.  
  63.       procedure Enqueue(aValue : integer);
  64.       procedure Push(aValue : integer);
  65.       function Pop : integer;
  66.   end;
  67. {--------}
  68. constructor TaaIntDeque.Create(aCapacity : integer);
  69. begin
  70.   inherited Create;
  71.   FList := TList.Create;
  72.   FList.Count := aCapacity;
  73.   {let's help out the user of the deque by putting the head and tail
  74.    pointers in the middle: it's more efficient}
  75.   FHead := aCapacity div 2;
  76.   FTail := FHead;
  77. end;
  78. {--------}
  79. destructor TaaIntDeque.Destroy;
  80. begin
  81.   FList.Free;
  82.   inherited Destroy;
  83. end;
  84. {--------}
  85. procedure TaaIntDeque.Enqueue(aValue : integer);
  86. begin
  87.   FList.List^[FTail] := pointer(aValue);
  88.   inc(FTail);
  89.   if (FTail = FList.Count) then
  90.     FTail := 0;
  91.   if (FTail = FHead) then
  92.     idGrow;
  93. end;
  94. {--------}
  95. procedure TaaIntDeque.idGrow;
  96. var
  97.   OldCount : integer;
  98.   i, j     : integer;
  99. begin
  100.   {grow the list by 50%}
  101.   OldCount := FList.Count;
  102.   FList.Count := (OldCount * 3) div 2;
  103.   {expand the data into the increased space, maintaining the deque}
  104.   if (FHead = 0) then
  105.     FTail := OldCount
  106.   else begin
  107.     j := FList.Count;
  108.     for i := pred(OldCount) downto FHead do begin
  109.       dec(j);
  110.       FList.List^[j] := FList.List^[i]
  111.     end;
  112.     FHead := j;
  113.   end;
  114. end;
  115. {--------}
  116. function TaaIntDeque.IsEmpty : boolean;
  117. begin
  118.   Result := FHead = FTail;
  119. end;
  120. {--------}
  121. procedure TaaIntDeque.Push(aValue : integer);
  122. begin
  123.   if (FHead = 0) then
  124.     FHead := FList.Count;
  125.   dec(FHead);
  126.   FList.List^[FHead] := pointer(aValue);
  127.   if (FTail = FHead) then
  128.     idGrow;
  129. end;
  130. {--------}
  131. function TaaIntDeque.Pop : integer;
  132. begin
  133.   if FHead = FTail then
  134.     raise Exception.Create('Integer deque is empty: cannot pop');
  135.   Result := integer(FList.List^[FHead]);
  136.   inc(FHead);
  137.   if (FHead = FList.Count) then
  138.     FHead := 0;
  139. end;
  140. {====================================================================}
  141.  
  142. function aaMatchRegEx(aTable  : PaaNFAStateTable;
  143.                       const S : string) : boolean;
  144. const
  145.   MustScan = -1;
  146. var
  147.   Ch    : char;
  148.   State : integer;
  149.   Deque : TaaIntDeque;
  150.   StrInx : integer;
  151. begin
  152.   {assume we fail to match}
  153.   Result := false;
  154.   {create the deque}
  155.   Deque := TaaIntDeque.Create(64);
  156.   try
  157.     {push the special value to start scanning}
  158.     Deque.Enqueue(MustScan);
  159.     {enqueue the first state}
  160.     Deque.Enqueue(aTable^.stStartState);
  161.     {prepare the string index}
  162.     StrInx := 0;
  163.     {loop until the deque is empty or we run out of string}
  164.     while (StrInx <= length(S)) and not Deque.IsEmpty do begin
  165.       {pop the top state from the deque}
  166.       State := Deque.Pop;
  167.       {process the "must scan" state first}
  168.       if (State = MustScan) then begin
  169.         {if the deque is empty at this point, we might as well give up
  170.          since there are no states left to process new characters}
  171.         if not Deque.IsEmpty then begin
  172.           {if we haven't run out of string, get the character, and
  173.            enqueue the "must scan" state again}
  174.           inc(StrInx);
  175.           if (StrInx <= length(S)) then begin
  176.             Ch := S[StrInx];
  177.             Deque.Enqueue(MustScan);
  178.           end;
  179.         end;
  180.       end
  181.       {otherwise, process the state}
  182.       else with aTable^.stTable[State] do begin
  183.         case sdMatchType of
  184.           mtNone :
  185.             begin
  186.               {for free moves, push the next states onto the deque}
  187.               if (sdNextState2 <> -1) then
  188.                 Deque.Push(sdNextState2);
  189.               if (sdNextState1 <> -1) then
  190.                 Deque.Push(sdNextState1);
  191.             end;
  192.           mtAnyChar :
  193.             begin
  194.               {for a match of any character, enqueue the next state}
  195.               Deque.Enqueue(sdNextState1);
  196.             end;
  197.           mtChar :
  198.             begin
  199.               {for a match of a character, enqueue the next state}
  200.               if (Ch = sdChar) then
  201.                 Deque.Enqueue(sdNextState1);
  202.             end;
  203.           mtClass :
  204.             begin
  205.               {for a match within a class, enqueue the next state}
  206.               if (Ch in sdClass^) then
  207.                 Deque.Enqueue(sdNextState1);
  208.             end;
  209.           mtNegClass :
  210.             begin
  211.               {for a match not within a class, enqueue the next state}
  212.               if not (Ch in sdClass^) then
  213.                 Deque.Enqueue(sdNextState1);
  214.             end;
  215.         end;
  216.       end;
  217.     end;
  218.     {if we reach this point we've either exhausted the deque or we've
  219.      run out of string; we need to check the states left on the deque
  220.      (if there are any) to see if one is the terminating state; if so
  221.      the string matched the regular expressionn defined by the
  222.      transition table}
  223.     while not Deque.IsEmpty do begin
  224.       State := Deque.Pop;
  225.       if (State = aTable^.stFinalState) then begin
  226.         Result := true;
  227.         Exit;
  228.       end;
  229.     end;
  230.   finally
  231.     Deque.Free;
  232.   end;
  233. end;
  234.  
  235.  
  236. var
  237.   Table : PaaNFAStateTable;
  238.   S     : string;
  239.  
  240. procedure SetEntry(aInx   : integer;
  241.                    aType  : TaaNFAMatchType;
  242.                    aChar  : char;
  243.                    aClass : PaaCharSet;
  244.                    aNext1 : integer;
  245.                    aNext2 : integer);
  246. begin
  247.   with Table^.stTable[aInx] do begin
  248.     sdNextState1 := aNext1;
  249.     sdMatchType  := aType;
  250.     case aType of
  251.       mtNone     : sdNextState2 := aNext2;
  252.       mtChar     : sdChar := aChar;
  253.       mtClass    : sdClass := aClass;
  254.       mtNegClass : sdClass := aClass;
  255.     end;
  256.   end;
  257. end;
  258.  
  259.  
  260. begin
  261.   {build the table}
  262.   Table := AllocMem(2 * sizeof(integer) +
  263.                     8 * sizeof(TaaNFAStateData));
  264.   SetEntry(0, mtChar, 'a', nil, 1, -1);
  265.   SetEntry(1, mtNone, ' ', nil, 3, -1);
  266.   SetEntry(2, mtChar, 'b', nil, 3, -1);
  267.   SetEntry(3, mtNone, ' ', nil, 4, 5);
  268.   SetEntry(4, mtNone, ' ', nil, 0, 2);
  269.   SetEntry(5, mtChar, 'b', nil, 6, -1);
  270.   SetEntry(6, mtChar, 'c', nil, 7, -1);
  271.   SetEntry(7, mtNone, ' ', nil, -1, -1);
  272.   Table^.stStartState := 3;
  273.   Table^.stFinalState := 7;
  274.  
  275.   writeln('Matching (a|b)*bc...');
  276.   S := 'bc';
  277.   writeln(S, ': ', aaMatchRegEx(Table, S));
  278.   S := 'abc';
  279.   writeln(S, ': ', aaMatchRegEx(Table, S));
  280.   S := 'aaaaaaaaaaabc';
  281.   writeln(S, ': ', aaMatchRegEx(Table, S));
  282.   S := 'bbbbbbbbbbbbbbbbc';
  283.   writeln(S, ': ', aaMatchRegEx(Table, S));
  284.   S := 'abababababababc';
  285.   writeln(S, ': ', aaMatchRegEx(Table, S));
  286.   S := 'bac';
  287.   writeln(S, ': ', aaMatchRegEx(Table, S));
  288.   S := 'cab';
  289.   writeln(S, ': ', aaMatchRegEx(Table, S));
  290.   readln;
  291.  
  292.   FreeMem(Table);
  293. end.
  294.